package com.dny.asmtop.op;

import com.dny.asmtop.ASMMethodUtils;
import com.dny.asmtop.CB;
import com.dny.asmtop.Command;
import com.dny.asmtop.MethodContext;
import jdk.internal.org.objectweb.asm.Type;
import jdk.internal.org.objectweb.asm.commons.GeneratorAdapter;
import jdk.internal.org.objectweb.asm.commons.Method;

import java.util.*;

import static java.lang.String.format;

/**
 * Created by jlutt on 2018-01-17.
 *
 * @author jlutt
 */
public class CommandCallStaticSelf implements Command {

  private final Command owner;
  private final String methodName;
  private final List<Command> arguments = new ArrayList<>();

  public CommandCallStaticSelf(String methodName, Command... expressions) {
    this.owner = CB.self();
    this.methodName = methodName;
    this.arguments.addAll(Arrays.asList(expressions));
  }

  @Override
  public Type type(MethodContext context) {
    List<Type> argumentTypes = new ArrayList<>();
    for (Command argument : arguments) {
      argumentTypes.add(argument.type(context));
    }

    Set<Method> methods = context.getStaticMethods().keySet();
    for (Method m : methods) {
      if (m.getName().equals(methodName)) {
        if (m.getArgumentTypes().length == argumentTypes.size()) {
          Type[] methodTypes = m.getArgumentTypes();
          boolean isSame = true;
          for (int i = 0; i < argumentTypes.size(); i++) {
            if (!methodTypes[i].equals(argumentTypes.get(i))) {
              isSame = false;
              break;
            }
          }
          if (isSame) {
            return m.getReturnType();
          }
        }
      }
    }
    throw new RuntimeException(format("No method %s.%s(%s). %s",
        owner.type(context).getClassName(),
        methodName,
        (!argumentTypes.isEmpty() ? ASMMethodUtils.argsToString(argumentClasses(context, arguments)) : ""),
        ASMMethodUtils.exceptionInGeneratedClass(context)));
  }

  private static List<Class<?>> argumentClasses(MethodContext context, List<Command> expressions) {
    List<Class<?>> classList = new ArrayList<>();
    for (Command expression : expressions) {
      classList.add(ASMMethodUtils.getJavaType(context.getClassLoader(), expression.type(context)));
    }
    return classList;
  }

  @Override
  public Type generator(MethodContext context) {
    GeneratorAdapter g = context.getGeneratorAdapter();
    Type ownerType = owner.type(context);

    List<Type> argumentTypes = new ArrayList<>();
    for (Command argument : arguments) {
      argument.generator(context);
      argumentTypes.add(argument.type(context));
    }

    Type returnType = type(context);
    g.invokeStatic(ownerType, new Method(methodName, returnType, argumentTypes.toArray(new Type[]{})));
    return returnType;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    CommandCallStaticSelf that = (CommandCallStaticSelf) o;
    return Objects.equals(owner, that.owner) &&
        Objects.equals(methodName, that.methodName) &&
        Objects.equals(arguments, that.arguments);
  }

  @Override
  public int hashCode() {
    return Objects.hash(owner, methodName, arguments);
  }
}
